import {
  RequestWillFetchCallbackParam,
  RouteMatchCallbackOptions,
} from "workbox-core/src/types";
import { addPrefix, equals, isEmpty, isHas, useState } from "jsmethod-extra";
import {
  CacheStrategy,
  CommonUtils,
  ExtractHtmlElement,
  HandleResponse,
  Logger,
  Storage,
  SystemConstants,
} from "../utils";

// 表示 root 目录的域名
const [rootOrigin, setRootOrigin] = useState("");

/**
 * @author lihh
 * @description 拿到 xxv 资源域
 * @param request 请求体
 */
async function getXXVResourceOrigin(request: Request): Promise<string> {
  const response = await CacheStrategy.yunkanCache.getCache(
    SystemConstants.MESSAGE_CHANNEL.GET_HTTP_URL_XXV
  );
  if (isEmpty(response)) {
    return new URL(request.url).origin;
  }

  const cloneResponse = response!.clone();
  return await cloneResponse.text();
}

/**
 * @author lihh
 * @description 处理请求
 * @param event fetch Event 事件
 * @param request 网络请求 request
 */
async function handleRequest({
  event,
  request,
}: RequestWillFetchCallbackParam): Promise<Response> {
  const searchParams = buildXXVURLSearchParams(request);
  if (
    equals(
      new URL(request.url).pathname,
      searchParams[SystemConstants.INTERCEPT_URL]
    )
  ) {
    return await rootPathHandleRequest(request, event as FetchEvent);
  }
  return await noRootPathHandleRequest(request, event as FetchEvent);
}

/**
 * @author lihh
 * @description 解析 html
 * @param request 请求 体
 * @param response fetch 查询到的响应体
 * @param fetchEvent event 事件
 */
async function resolveHTML(
  request: Request,
  response: Response,
  fetchEvent: FetchEvent
) {
  const text = await CommonUtils.readHTMLStream(response);
  const rootPathname = await getProxyURL(request);
  const elements = ExtractHtmlElement.getInstance(text).all();
  const clientID = CommonUtils.getClientID(fetchEvent);

  Storage.xxvResourceFetchClients.set(clientID, [rootPathname, elements]);
}

/**
 * @author lihh
 * @description 构建 xxv url searchParams
 * @param request 请求体
 */
function buildXXVURLSearchParams(request: Request): Record<string, string> {
  return CommonUtils.buildURLSearchParams(
    request.url.includes(SystemConstants.PROXY_URL)
      ? request.url
      : request.referrer
  );
}

/**
 * @author lihh
 * @description 拿到代理地址
 * @param request 请求 request体
 */
async function getProxyURL(request: Request): Promise<string> {
  const searchParams = buildXXVURLSearchParams(request);
  const proxyURL = searchParams[SystemConstants.PROXY_URL];

  const length = SystemConstants.SLASH + SystemConstants.INDEX_NAME;
  return proxyURL.endsWith(SystemConstants.SLASH + SystemConstants.INDEX_NAME)
    ? proxyURL.slice(0, proxyURL.length - length)
    : proxyURL;
}

/**
 * @author lihh
 * @description 跟目录的处理
 * @param request 请求的request
 * @param fetchEvent 请求 event 事件
 */
async function rootPathHandleRequest(
  request: Request,
  fetchEvent: FetchEvent
): Promise<Response> {
  Logger.info(request.url);

  const xxvResourceOrigin = await getXXVResourceOrigin(request);
  setRootOrigin(xxvResourceOrigin);

  const pathname = await getProxyURL(request);
  const path = addPrefix(pathname, xxvResourceOrigin);
  const finalPath = addPrefix(SystemConstants.INDEX_NAME, path);

  const response = await fetch(finalPath);
  await resolveHTML(request, response, fetchEvent);
  return await HandleResponse.getInstance().handle(response, path);
}

/**
 * @author lihh
 * @description 非根目录下 处理
 * @param request 请求体
 * @param fetchEvent 请求 event体
 */
async function noRootPathHandleRequest(
  request: Request,
  fetchEvent: FetchEvent
) {
  Logger.info(request.url);

  const rootPathname = addPrefix(
    Storage.xxvResourceFetchClients.get(
      CommonUtils.getClientID(fetchEvent)
    )![0],
    SystemConstants.SLASH
  );
  const currentPathname = addPrefix(
    new URL(request.url).pathname,
    SystemConstants.SLASH
  );
  const finalPathname = currentPathname.startsWith(rootPathname)
    ? currentPathname
    : addPrefix(currentPathname, rootPathname);
  const path = addPrefix(finalPathname, rootOrigin.current);

  const response = await fetch(path);
  return await HandleResponse.getInstance().handle(response, path);
}

/**
 * @author lihh
 * @description 构建 捕获条件
 * @param params route 参数
 */
function buildCaptureCondition(params: RouteMatchCallbackOptions): boolean {
  // 1. 排除一些特殊的 URL
  if (
    SystemConstants.EXCLUDE_PATH.some((l) => params.request.url.includes(l)) ||
    CommonUtils.isNotGet(params.request)
  ) {
    return false;
  }

  const fetchEvent = params.event as FetchEvent;
  // 2. 特殊的参数判断
  /**
   * URL 的searchParams 中包含 identification = xxv &&
   * 访问的路径的根目录是 /
   */
  const searchParams = buildXXVURLSearchParams(params.request);
  if (
    equals(searchParams[SystemConstants.IDENTIFICATION], SystemConstants.XXV) &&
    isHas(searchParams, SystemConstants.INTERCEPT_URL) &&
    equals(
      new URL(params.request.url).pathname,
      searchParams[SystemConstants.INTERCEPT_URL]
    )
  ) {
    return true;
  }

  // 3. 判断 clientId 是否同一个
  const values = Storage.xxvResourceFetchClients.get(
    CommonUtils.getClientID(fetchEvent)
  );
  if (
    isEmpty(values) ||
    !Storage.xxvResourceFetchClients.has(CommonUtils.getClientID(fetchEvent))
  )
    return false;

  const elements = [...values[1], ...SystemConstants.ALLOW_FILE_SUFFIX],
    path = params.request.url;
  return elements.some((l) => path.endsWith(l));
}

/**
 * @author lihh
 * @description fetch 拦截器插件
 */
export const xxvResourceInterceptorPlugin = () => ({
  capture: buildCaptureCondition,
  handler: handleRequest,
});
